#include "types.h"
#include "defs.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "x86.h"
#include "proc.h"
#include "spinlock.h"
#include "kthread.h"

struct {
  struct spinlock lock;
  struct proc proc[NPROC];
} ptable;


static struct { // Mutexes table
  struct spinlock lock;
  kthread_mutex_t mutexes[MAX_MUTEXES];
} mtable;

static struct { // Condition Variables table
  struct spinlock lock;
  kthread_cond_t conditionVariables[MAX_CONDS];
} cvtable;

static struct proc *initproc;

int nextpid = 1;

extern void forkret(void);
extern void trapret(void);

static void wakeup1(void *chan);

void
pinit(void)
{
  initlock(&ptable.lock, "ptable");
}

//PAGEBREAK: 32
// Look in the process table for an UNUSED proc.
// If found, change state to EMBRYO and initialize
// state required to run in the kernel.
// Otherwise return 0.
static struct proc*
allocproc(void)
{
  struct proc *p;
  char *sp;

  acquire(&ptable.lock);
  for(p = ptable.proc; p < &ptable.proc[NPROC]; p++)
    if(p->state == UNUSED)
      goto found;
  release(&ptable.lock);
  return 0;

found:
  p->state = EMBRYO;
  p->pid = nextpid++;
  p->containInProcess=p->pid; // thread family id.
  p->rtime = 0;
  p->etime = 0;
  
  /*** Process create time ***/
  acquire(&tickslock);
  p->ctime = ticks;
  release(&tickslock);
  /***/
  
  release(&ptable.lock);

  // Allocate kernel stack.
  if((p->kstack = kalloc()) == 0){
    p->state = UNUSED;
    return 0;
  }
  sp = p->kstack + KSTACKSIZE;
  
  // Leave room for trap frame.
  sp -= sizeof *p->tf;
  p->tf = (struct trapframe*)sp;
  
  // Set up new context to start executing at forkret,
  // which returns to trapret.
  sp -= 4;
  *(uint*)sp = (uint)trapret;

  sp -= sizeof *p->context;
  p->context = (struct context*)sp;
  memset(p->context, 0, sizeof *p->context);
  p->context->eip = (uint)forkret;
  
  return p;
}

//PAGEBREAK: 32
// Set up first user process.
void
userinit(void)
{
  struct proc *p;
  extern char _binary_initcode_start[], _binary_initcode_size[];
  
  
  /***** initalize the mutex and condition variables static arrays *****/
    int i;
    acquire(&mtable.lock);
    for(i=0;i<MAX_MUTEXES;i++)
		mtable.mutexes[i].mid=-1;

    release(&mtable.lock);
    
    acquire(&cvtable.lock);
    for(i=0;i<MAX_CONDS;i++)
	    cvtable.conditionVariables[i].cid=-1;
	
    release(&cvtable.lock);
  /*******/
  
  p = allocproc();
  initproc = p;
  if((p->pgdir = setupkvm(kalloc)) == 0)
    panic("userinit: out of memory?");
  inituvm(p->pgdir, _binary_initcode_start, (int)_binary_initcode_size);
  p->sz = PGSIZE;
  memset(p->tf, 0, sizeof(*p->tf));
  p->tf->cs = (SEG_UCODE << 3) | DPL_USER;
  p->tf->ds = (SEG_UDATA << 3) | DPL_USER;
  p->tf->es = p->tf->ds;
  p->tf->ss = p->tf->ds;
  p->tf->eflags = FL_IF;
  p->tf->esp = PGSIZE;
  p->tf->eip = 0;  // beginning of initcode.S

  safestrcpy(p->name, "initcode", sizeof(p->name));
  p->cwd = namei("/");

  p->state = RUNNABLE;
}

// Grow current process's memory by n bytes.
// Return 0 on success, -1 on failure.
int
growproc(int n)
{
  uint sz;
  
  sz = proc->sz;
  if(n > 0){
    if((sz = allocuvm(proc->pgdir, sz, sz + n)) == 0)
      return -1;
  } else if(n < 0){
    if((sz = deallocuvm(proc->pgdir, sz, sz + n)) == 0)
      return -1;
  }
  proc->sz = sz;
  switchuvm(proc);
  return 0;
}

// Create a new process copying p as the parent.
// Sets up stack to return as if from system call.
// Caller must set state of returned proc to RUNNABLE.
int
fork(void)
{
  int i, pid;
  struct proc *np;

  // Allocate process.
  if((np = allocproc()) == 0)
    return -1;

  // Copy process state from p.
  if((np->pgdir = copyuvm(proc->pgdir, proc->sz)) == 0){
    kfree(np->kstack);
    np->kstack = 0;
    np->state = UNUSED;
    return -1;
  }
  np->sz = proc->sz;
  np->parent = proc;
  *np->tf = *proc->tf;

  // Clear %eax so that fork returns 0 in the child.
  np->tf->eax = 0;

  for(i = 0; i < NOFILE; i++)
    if(proc->ofile[i])
      np->ofile[i] = filedup(proc->ofile[i]);
  np->cwd = idup(proc->cwd);
 
  pid = np->pid;
  np->state = RUNNABLE;
  safestrcpy(np->name, proc->name, sizeof(proc->name));
  return pid;
}

// Exit the current process.  Does not return.
// An exited process remains in the zombie state
// until its parent calls wait() to find out it exited.
void
exit(void)
{
    struct proc *p;
    int fd;

    if(proc == initproc)
		panic("init exiting");
   
	// check all the threads in the family and 
    for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
		if(p->containInProcess==proc->containInProcess){
			// Close all open files.
			for(fd = 0; fd < NOFILE; fd++){
				if(proc->ofile[fd]){
					fileclose(proc->ofile[fd]);
					proc->ofile[fd] = 0;
				}
			}
			p->state=ZOMBIE;
		}
    }

    iput(proc->cwd);
    proc->cwd = 0;

    acquire(&ptable.lock);

    // Parent might be sleeping in wait().
    wakeup1(proc->parent);

    // Pass abandoned children to init.
    for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
		if(p->parent == proc){
			p->parent = initproc;
		if(p->state == ZOMBIE)
			wakeup1(initproc);
		}
    }

    /*** Process end time ***/
    acquire(&tickslock);
    proc->etime = ticks;
    release(&tickslock);
    /***/
	
    sched();
    panic("zombie exit");
}

// Wait for a child process to exit and return its pid.
// Return -1 if this process has no children.
int
wait(void)
{
  struct proc *p;
  struct proc *t;
  int havekids,pid;
  int allThreadsZombies;
  int true = 0;
  
  acquire(&ptable.lock);
  for(;;){
    // Scan through table looking for zombie children.    
    havekids = 0;
    for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
		allThreadsZombies = 0;
		if(p->parent != proc)
			continue;

		havekids = 1;
		for(t = ptable.proc; t < &ptable.proc[NPROC]; t++){ // find all the threads in the family and check if all zombies then release all the relevant resources
			if(t->containInProcess == p->containInProcess)
				if (t->state != ZOMBIE){
					allThreadsZombies++;
					break;
				}
		}
	
		pid = p->containInProcess;
		if (allThreadsZombies == true){
			for(t = ptable.proc; t < &ptable.proc[NPROC]; t++){
				if (t->containInProcess == pid){
					kfree(t->kstack);
					t->kstack = 0;
					t->state = UNUSED;
					t->pid = 0;
					t->parent = 0;
					t->name[0] = 0;
					t->killed = 0;
					t->containInProcess = 0;
					t->performJoin = 0;
				}
			}
	    freevm(p->pgdir);
	    pid = proc->pid;
	    release(&ptable.lock);
	    return pid;
		}
    }
    
    if (havekids == 0 || proc->killed){
		release(&ptable.lock);
		return -1;
    }
    
    sleep(proc,&ptable.lock);
  }
  
  return -1;
}

void
register_handler(sighandler_t sighandler)
{
  char* addr = uva2ka(proc->pgdir, (char*)proc->tf->esp);
  if ((proc->tf->esp & 0xFFF) == 0)
    panic("esp_offset == 0");

    /* open a new frame */
  *(int*)(addr + ((proc->tf->esp - 4) & 0xFFF))
          = proc->tf->eip;
  proc->tf->esp -= 4;

    /* update eip */
  proc->tf->eip = (uint)sighandler;
}

//PAGEBREAK: 42
// Per-CPU process scheduler.
// Each CPU calls scheduler() after setting itself up.
// Scheduler never returns.  It loops, doing:
//  - choose a process to run
//  - swtch to start running that process
//  - eventually that process transfers control
//      via swtch back to the scheduler.
void
scheduler(void)
{
  struct proc *p;

  for(;;){
    // Enable interrupts on this processor.
    sti();

    // Loop over process table looking for process to run.
    acquire(&ptable.lock);
    for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
      if(p->state != RUNNABLE)
        continue;

      // Switch to chosen process.  It is the process's job
      // to release ptable.lock and then reacquire it
      // before jumping back to us.
      proc = p;
      switchuvm(p);
      p->state = RUNNING;
      swtch(&cpu->scheduler, proc->context);
      switchkvm();

      // Process is done running for now.
      // It should have changed its p->state before coming back.
      proc = 0;
    }
    release(&ptable.lock);

  }
}

// Enter scheduler.  Must hold only ptable.lock
// and have changed proc->state.
void
sched(void)
{
  int intena;

  if(!holding(&ptable.lock))
    panic("sched ptable.lock");
  if(cpu->ncli != 1)
    panic("sched locks");
  if(proc->state == RUNNING)
    panic("sched running");
  if(readeflags()&FL_IF)
    panic("sched interruptible");

  intena = cpu->intena;
  swtch(&proc->context, cpu->scheduler);

  cpu->intena = intena;
}

// Give up the CPU for one scheduling round.
void
yield(void)
{
  acquire(&ptable.lock);  //DOC: yieldlock
  proc->state = RUNNABLE;
  sched();
  release(&ptable.lock);
}

// A fork child's very first scheduling by scheduler()
// will swtch here.  "Return" to user space.
void
forkret(void)
{
  static int first = 1;
  // Still holding ptable.lock from scheduler.
  release(&ptable.lock);

  if (first) {
    // Some initialization functions must be run in the context
    // of a regular process (e.g., they call sleep), and thus cannot 
    // be run from main().
    first = 0;
    initlog();
  }
  
  // Return to "caller", actually trapret (see allocproc).
}

// Atomically release lock and sleep on chan.
// Reacquires lock when awakened.
void
sleep(void *chan, struct spinlock *lk)
{
  if(proc == 0)
    panic("sleep");

  if(lk == 0)
    panic("sleep without lk");

  // Must acquire ptable.lock in order to
  // change p->state and then call sched.
  // Once we hold ptable.lock, we can be
  // guaranteed that we won't miss any wakeup
  // (wakeup runs with ptable.lock locked),
  // so it's okay to release lk.
  if(lk != &ptable.lock){  //DOC: sleeplock0
    acquire(&ptable.lock);  //DOC: sleeplock1
    release(lk);
  }

  // Go to sleep.
  proc->chan = chan;
  proc->state = SLEEPING;
  sched();

  // Tidy up.
  proc->chan = 0;

  // Reacquire original lock.
  if(lk != &ptable.lock){  //DOC: sleeplock2
    release(&ptable.lock);
    acquire(lk);
  }
}

//PAGEBREAK!
// Wake up all processes sleeping on chan.
// The ptable lock must be held.
static void
wakeup1(void *chan)
{
  struct proc *p;

  for(p = ptable.proc; p < &ptable.proc[NPROC]; p++)
    if((p->state == SLEEPING || p->state == BLOCKED )&& p->chan == chan) // we added the state BLOCKED for the mutexes and condition variables.
      p->state = RUNNABLE;
}

// Wake up all processes sleeping on chan.
void
wakeup(void *chan)
{
  acquire(&ptable.lock);
  wakeup1(chan);
  release(&ptable.lock);
}

// Kill the process with the given pid.
// Process won't exit until it returns
// to user space (see trap in trap.c).
int
kill(int pid)
{
  struct proc *p;

  acquire(&ptable.lock);
  for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
    if(p->pid == pid){
      p->killed = 1;
      // Wake process from sleep if necessary.
      if(p->state == SLEEPING)
        p->state = RUNNABLE;
      release(&ptable.lock);
      return 0;
    }
  }
  release(&ptable.lock);
  return -1;
}

//PAGEBREAK: 36
// Print a process listing to console.  For debugging.
// Runs when user types ^P on console.
// No lock to avoid wedging a stuck machine further.
void
procdump(void)
{
  static char *states[] = {
  [UNUSED]    "unused",
  [EMBRYO]    "embryo",
  [SLEEPING]  "sleep ",
  [RUNNABLE]  "runble",
  [RUNNING]   "run   ",
  [ZOMBIE]    "zombie"
  };
  int i;
  struct proc *p;
  char *state;
  uint pc[10];
  
  for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
    if(p->state == UNUSED)
      continue;
    if(p->state >= 0 && p->state < NELEM(states) && states[p->state])
      state = states[p->state];
    else
      state = "???";
    cprintf("%d %s %s", p->pid, state, p->name);
    if(p->state == SLEEPING){
      getcallerpcs((uint*)p->context->ebp+2, pc);
      for(i=0; i<10 && pc[i] != 0; i++)
        cprintf(" %p", pc[i]);
    }
    cprintf("\n");
  }
}

int 
kthread_create(void*(*start_func)(), void* stack, uint stack_size){
    int i;
    struct proc *nt;

    // Allocate process.
    if((nt = allocproc()) == 0) 
		return -1;
    
    nt->pgdir = proc->pgdir;  
    nt->containInProcess = proc->containInProcess;
    nt->performJoin = 0;
    nt->parent = proc->parent;
    nt->sz = proc->sz;
    *nt->tf = *proc->tf;

    // Clear %eax so that fork returns 0 in the child.
    nt->tf->eax = 0;

    nt->tf->eip = (uint)start_func; // Defining the instruction pointer to point on the start function.
    nt->tf->esp = (uint)(stack+stack_size); // Defining the stack pointer to point on the given stack. 
    
    for(i = 0; i < NOFILE; i++)
		if(proc->ofile[i])
			nt->ofile[i] = proc->ofile[i];
    
    nt->cwd = proc->cwd;
    
    nt->state = RUNNABLE;
    safestrcpy(nt->name, proc->name, sizeof(proc->name));
    return nt->pid;
}

int
kthread_id(){
    if (proc!=0)
		return proc->pid;
    return -1;
}

void
kthread_exit(){
    struct proc *th;
    int numberOfActiveThreads=0;

    acquire(&ptable.lock);

    // Counts the number of threads with the same Process "family" id.
    for(th = ptable.proc; th < &ptable.proc[NPROC]; th++){
		if(th->containInProcess == proc->containInProcess && th->state!=ZOMBIE)
			numberOfActiveThreads++;
    }

    // The last thread in the process.
    if (numberOfActiveThreads == 1){
		release(&ptable.lock);
		exit();
    }

    wakeup1(proc);
	  
    // Jump into the scheduler, never to return.
    proc->state = ZOMBIE;
    
    /*** Process end time ***/
    acquire(&tickslock);
    proc->etime = ticks;
    release(&tickslock);
    /***/
	
    sched();
    panic("zombie exit");
}

int 
kthread_join(int thread_id){
    int true = 1;
    struct proc * th = 0;
    int valid = 0;
	
    acquire(&ptable.lock);
    
    for(th = ptable.proc; th < &ptable.proc[NPROC]; th++){ // check if the thread_id is in zombie state
		if (th->pid == thread_id && th->state != ZOMBIE){
			valid = 1;
			break;
		}
    }
    
    if (valid == true){ // start sleep, the thread you join isn't zombie
		while (th->state != ZOMBIE) {
			sleep(th,&ptable.lock);
		}
		release(&ptable.lock);
		return 0;
    }
    
    release(&ptable.lock);

    return -1;   
}

// mutexes and condition variables Id index
int nextmutexid = 1;
int nextcondid = 1;


int kthread_mutex_wait(kthread_mutex_t * mutex,struct spinlock * lk){
    int i = mutex->threadIndex; // threadIndex is the pointer to the old thread in the waiting list
    int counter = 0;
	
	while(counter < NPROC){ // place the new thread on the waiting list in an circular mode start from threadIndex
		if(mutex->waitingList[i % NPROC] == 0){
			mutex->waitingList[i % NPROC] = proc;
			break;
		}
	i++;
	counter++;
    }
	
    if(counter == NPROC)
		return -1;
    
    if(proc == 0)
		panic("sleep");

    if(lk == 0)
		panic("sleep without lk");

    // Must acquire ptable.lock in order to
    // change p->state and then call sched.
    // Once we hold ptable.lock, we can be
    // guaranteed that we won't miss any wakeup
    // (wakeup runs with ptable.lock locked),
    // so it's okay to release lk.
    if(lk != &ptable.lock){  //DOC: sleeplock0
		acquire(&ptable.lock);  //DOC: sleeplock1
		release(lk);
    }

    // Go to sleep.
    proc->chan = proc;
    proc->state = BLOCKED;
    sched();

    // Tidy up.
    proc->chan = 0;

    // Reacquire original lock.
    if(lk != &ptable.lock){  //DOC: sleeplock2
		release(&ptable.lock);
		acquire(lk);
    }

    return 0;
}

int kthread_mutex_notify(kthread_mutex_t * mutex){
    int i = (mutex->threadIndex) % NPROC; // pointer to the old thread in the waiting list
    int counter = 0;
    
    while(counter < NPROC){
		if(mutex->waitingList[i % NPROC] != 0){
			wakeup(mutex->waitingList[i % NPROC]);
			mutex->waitingList[i % NPROC] = 0;
			mutex->threadIndex = (mutex->threadIndex+1) % NPROC; // increment the threadIndex to the new old thread in the waiting list
			break;
		}
	counter++;
    }
    
    return 0;
}

int kthread_mutex_alloc(){  
    int i;
    kthread_mutex_t * mtx = 0;
    
    acquire(&mtable.lock);

    for(i = 0; i < MAX_MUTEXES; i++){ // check for place in the mutexes table
		if (mtable.mutexes[i].mid == -1){
			mtx = &mtable.mutexes[i];
			break;
		}
    }
    
    if (!mtx) {
		release(&mtable.lock);
		return -1;
    }
    
    // Initalize fields in the mutex
    mtx->mid = nextmutexid++;
    mtx->locked = 0;
    mtx->ownedBy = 0;
    mtx->threadIndex = 0;
   
    int mutexId = mtx->mid;
    release(&mtable.lock);
    
    return mutexId;
}

int kthread_mutex_dealloc(int mutex_id){
    int i;
    kthread_mutex_t * mtx = 0;
    
    acquire(&mtable.lock);

    for(i = 0; i < MAX_MUTEXES; i++){ // check if the mutex_id exists
		if (mtable.mutexes[i].mid == mutex_id){
			mtx = &mtable.mutexes[i];
			break;
		}
    }
    
    if (!mtx){
		goto dealloc_abnomarl_end;
    }
	
    int waitingListSize = sizeof(mtx->waitingList)/sizeof(struct proc);
    
    if(waitingListSize != 0){ // can't dealloc mutex while threads wait for it.
		goto dealloc_abnomarl_end;
    }
    
    mtx->mid = -1;
    mtx->locked = 0;
    mtx->ownedBy = 0;
    mtx->threadIndex = 0;
    release(&mtable.lock);

    return 0;
    
dealloc_abnomarl_end:
    release(&mtable.lock);
    return -1;
}

int kthread_mutex_lock(int mutex_id){
    int i;
    kthread_mutex_t * mtx = 0;
   
    acquire(&mtable.lock);
	
    for(i = 0; i < MAX_MUTEXES; i++) { // check if the mutex_id exists
		if(mtable.mutexes[i].mid == mutex_id){
			mtx = &mtable.mutexes[i];
			break;
		}
    }

    if (!mtx || mtx->ownedBy == proc) {
		goto lock_abnomarl_end;
    }

	if (mtx->locked == 1){
		if (kthread_mutex_wait(mtx,&mtable.lock) < 0){
			goto lock_abnomarl_end;
		}
	}
	
    mtx->locked = 1;
    mtx->ownedBy = proc;
    release(&mtable.lock);
	
    return 0;
    
lock_abnomarl_end:
    release(&mtable.lock);
    return -1;
}

int kthread_mutex_unlock(int mutex_id){
    int i;
    kthread_mutex_t * mtx = 0;
    
    acquire(&mtable.lock);

    for(i = 0; i < MAX_MUTEXES; i++){ // check if the mutex_id exists
		if(mtable.mutexes[i].mid == mutex_id){
			mtx = &mtable.mutexes[i];
			break;
		}
    }

    if(!mtx || mtx->locked == 0 || mtx->ownedBy != proc){
		goto unlock_abnomarl_end;
    }
	
	if (kthread_mutex_notify(mtx) < 0){
		goto unlock_abnomarl_end;
	}

    mtx->locked = 0;
    mtx->ownedBy = 0;
	
    release(&mtable.lock);
	
    return 0;
    
unlock_abnomarl_end:
    release(&mtable.lock);
    return -1;
}

int kthread_cond_notify(kthread_cond_t * cv){
	int i = (cv->threadIndex) % NPROC; // pointer to the old thread in the waiting list
    int counter = 0;
		
    while(counter < NPROC){
		if(cv->condWaitingList[i % NPROC] != 0){
			wakeup(cv->condWaitingList[i % NPROC]);
			cv->condWaitingList[i % NPROC] = 0;
			cv->threadIndex = (cv->threadIndex+1) % NPROC;
			break;
		}
		counter++;
    }

    return 0;
}

int kthread_cond_sleep(kthread_cond_t * cv,struct spinlock * lk){
    int i = cv->threadIndex; // pointer to the old thread in the waiting list
    int counter = 0;
	
	while(counter<NPROC){
		if(cv->condWaitingList[i % NPROC] == 0){
			cv->condWaitingList[i % NPROC] = proc;
			break;
		}
		i++;
		counter++;
    }
	
    if(counter == NPROC)
		return -1;
    
    if(proc == 0)
		panic("sleep");

    if(lk == 0)
		panic("sleep without lk");

    // Must acquire ptable.lock in order to
    // change p->state and then call sched.
    // Once we hold ptable.lock, we can be
    // guaranteed that we won't miss any wakeup
    // (wakeup runs with ptable.lock locked),
    // so it's okay to release lk.
    if(lk != &ptable.lock){  //DOC: sleeplock0
		acquire(&ptable.lock);  //DOC: sleeplock1
		release(lk);
    }

    // Go to sleep.
    proc->chan = proc;
    proc->state = BLOCKED;
    sched();
	
    // Tidy up.
    proc->chan = 0;

    // Reacquire original lock.
    if(lk != &ptable.lock){  //DOC: sleeplock2
		release(&ptable.lock);
		acquire(lk);
    }

    return 0;
}

int kthread_cond_alloc(){
    int i;
    kthread_cond_t * cv = 0;
    
    acquire(&cvtable.lock);

    for(i = 0; i < MAX_CONDS; i++){ // check for place in the conditionVariables table
		if (cvtable.conditionVariables[i].cid == -1){
			cv = &cvtable.conditionVariables[i];
			break;
		}
    }
    
    if (cv->cid != -1){
		release(&cvtable.lock);
		return -1;
    }
	
    cv->cid = nextcondid++;
    cv->threadIndex = 0;

    int cvId = cv->cid;
    
    release(&cvtable.lock);
	
    return cvId;
}

int kthread_cond_dealloc(int cond_id){
    int i;
    kthread_cond_t * cv = 0;
    
    acquire(&cvtable.lock);

    for(i = 0; i < MAX_CONDS; i++){ // check if the cond_id exists
		if (cvtable.conditionVariables[i].cid == cond_id){
			cv = &cvtable.conditionVariables[i];
			break;
		}
    }   

    if (!cv){
		release(&cvtable.lock);
		return -1;
    }
    
    int waitingListSize = sizeof(cv->condWaitingList) / sizeof(struct proc);
    if(waitingListSize != 0){ // can't dealloc condition variable while threads wait for it.
		release(&cvtable.lock);
		return -1;
    }
	
    cv->cid = -1;
    cv->threadIndex = 0;
    
    release(&cvtable.lock);

    return 0;
}

int kthread_cond_wait(int cond_id, int mutex_id){
    int i;
    kthread_cond_t * cv = 0;
    kthread_mutex_t * mtx = 0;
     
    acquire(&mtable.lock);
	
    for(i = 0; i < MAX_MUTEXES; i++){ // check if the mutex_id exists
		if (mtable.mutexes[i].mid == mutex_id) {
			mtx = &mtable.mutexes[i];
			break;
		}
    }
    
    if(!mtx || mtx->locked == 0 || mtx->ownedBy != proc){
		release(&mtable.lock);
		return -1;
    }

	mtx = 0;
    release(&mtable.lock);
    
    acquire(&cvtable.lock);
	
    for(i = 0; i < MAX_CONDS; i++){ // check if the cond_id exists
		if(cvtable.conditionVariables[i].cid == cond_id){
			cv = &cvtable.conditionVariables[i];
			break;
		}
    }
    
    if (!cv){
		goto wait_abnomarl_end;
    }

    if (kthread_mutex_unlock(mutex_id) < 0){
		goto wait_abnomarl_end;
    }

    if (kthread_cond_sleep(cv,&cvtable.lock) < 0){
		goto wait_abnomarl_end;
    }
    
    release(&cvtable.lock);

    if (kthread_mutex_lock(mutex_id) < 0){
		goto wait_abnomarl_end;
    }    
    
    return 0;
    
wait_abnomarl_end:
    release(&cvtable.lock);
    return -1;
}

int kthread_cond_signal(int cond_id){
    int i;
    kthread_cond_t * cv = 0;
    	
    acquire(&cvtable.lock);
	
    for(i = 0;i < MAX_CONDS; i++){
		if(cvtable.conditionVariables[i].cid == cond_id){
			cv = &cvtable.conditionVariables[i];
			break;
		}
    }

    if (!cv){
		goto signal_abnomarl_end;
    }
    
    if (kthread_cond_notify(cv) < 0){
		goto signal_abnomarl_end;
    }
	
    release(&cvtable.lock);

    return 0;
    
signal_abnomarl_end:
    release(&cvtable.lock);
    return -1;
}

int retrieve_process_statistics(int* totalElapsedTime,int* totalRunTime,int* totalWaitTime){
    acquire(&ptable.lock);

    *totalElapsedTime = ticks - proc->ctime;
    *totalRunTime = proc->rtime;
    *totalWaitTime = *totalElapsedTime - proc->rtime;

    release(&ptable.lock);
    
    return 0;
}